Skip to content

feat: Button variant ghost — implements nuri-com/nuri-design-system#233 - #234

Merged
darioaschero merged 1 commit into
mainfrom
feat/button-ghost
Oct 29, 2026
Merged

darioaschero merged 1 commit into
mainfrom
feat/button-ghost

Conversation

@darioaschero

Copy link
Copy Markdown
Collaborator

Implements #233: Button variant: 'ghost'.

  • Admission: decision, Extension, sole-architect exception, with the independent review and the amendment.
  • Ready: Project Ready was confirmed by the operator on 2026-10-29. The gh token has no read:project scope, so this PR does not verify it.
  • Base: origin/main at 111aaab.

It reverses #154 (08030f6) for Button ghost only. #154's stack fill/distribute and alert hug stay.

Contract delivered

  • ButtonProps.variant is now 'solid' | 'soft' | 'ghost'. The default stays soft.
  • Ghost composes root.palette.variant: 'ghost': transparent at rest, text-primary text, bg-subtle when pressed.
  • Ghost shares everything else with solid and soft: anatomy, sizes (sm 36, lg 60), padding, fill, slots, press scale and disabled opacity.
  • accent is accepted on ghost and paints nothing. This is documented, not excluded in the types.
  • Anti-scope respected: no palette row, token or CSS; no schema change (the Guard F pin did not move); no size, padding or hitSlop change; IconButton untouched; no amount-switch component.

Changes

Area File(s)
Spec packages/spec/components/button.ts: ghost added to ButtonAxes.variant and variants.variant
Generated Descriptor twin packages/prototype/generated/descriptors/button.js · RN union packages/rn/generated/components/button.ts · doc variant row and lead packages/doc/generated/components/button.md. No CSS, RN data, playground bundle or snapshot changed.
Guards Guard D axis pin scripts/docs-drift.test.js:183 · doc page-contract pin, plus the lead's four points pinned, in packages/doc/pipeline/docs-drift.test.js
RN tests resolve: the 3 ghost cells #154 removed (bg transparent · pressed bg-subtle · fg text-primary), plus ghost≡soft geometry per size and accent invariance in light and dark · geometry-bake: pins that the cross-product covers ghost × sm/lg × fill (18 cells) · render-smoke: new ghost cell (light and dark, 36/60, pressed, disabled, accent) · type-tests: exact variant union, ghost usage, ghost + accent compiles
Docs Button page lead, in packages/doc/pipeline/component-api-ir.js (the hand-authored lead). It covers: lowest emphasis (tertiary) · an action inside content, where a fill would compete with it · never a screen's main or only action · accent does not paint it.
Playground pages/component/button.html: Ghost in the variant board, plus a ghost proof board (sm ⇅ ₿ 27 435, lg, lg accent="orange", lg disabled)
expo-demo src/screens/Bitcoin.tsx: the other-unit amount under the ₿ balance is a sm ghost swap button (transfer-vertical + amount). A tap swaps the units, and the screen owns the state.

A note on "put back the ghost cells". Before #154, only resolve.test.ts had Button ghost cells. render-smoke and type-tests never had one: #154 only repointed md→lg there. geometry-bake derives the cross-product from the descriptor, so it covers ghost automatically. The resolve cells are restored verbatim. The render-smoke, type-test and geometry-bake cells are new.

Verification (commands and output)

All commands were run in a fresh worktree (from origin/main + npm ci) on the tree committed as e5ea0ba.

$ node --test scripts/*.test.js
# tests 128 · pass 128 · fail 0

$ npm test -w @nuri/prototype
# tests 130 · pass 130 · fail 0

$ npm test -w @nuri/doc
# tests 3 · pass 3 · fail 0

$ npm test -w @nuri/rn
Test Suites: 21 passed, 21 total
Tests:       308 passed, 308 total
Snapshots:   26 passed, 26 total        # none written, none updated

$ npm run typecheck -w @nuri/rn          # tsc → exit 0
$ npm run typecheck -w @nuri/expo-demo   # tsc → exit 0

Deterministic re-emit. I ran npm run build && npm run build -w @nuri/doc twice and hashed every file under packages/{rn,prototype,doc,playground}/generated, packages/prototype/styles and packages/spec (163 files) with sha256:

build 1 manifest sha256: 3642cc76fabd03b8959a51be85a84805b84b035abd72839f44cb5f90508333bd
build 2 manifest sha256: 3642cc76fabd03b8959a51be85a84805b84b035abd72839f44cb5f90508333bd
diff → IDENTICAL across two builds

After the builds, git status shows only the three intended generated files changed.

solid and soft are unchanged. There is no diff in packages/rn/__tests__/__snapshots__/, packages/prototype/styles/, packages/playground/generated/ or packages/rn/generated/data/.

Mutation check (the new cells bind). I changed the spec's ghost row to palette: { variant: 'soft' } and rebuilt. resolve and render-smoke then report Tests: 4 failed, 111 passed. After restoring the row and rebuilding, the generated hashes are identical to the pre-mutation manifest.

Web probe (playground component/button.html, computed styles)

Cell Rest bg Text Pressed bg (real :active) Height Opacity
ghost sm, light rgba(0,0,0,0) rgb(34,32,19) = #222013 rgb(251,249,238) = --nuri-bg-subtle #fbf9ee, scale 0.97 36 1
ghost lg, light transparent #222013 #fbf9ee, scale 0.97 60 1
ghost lg accent="orange", light transparent #222013 #fbf9ee, scale 0.97 60 1
ghost lg disabled transparent #222013 press gates dropped (data-press-color absent) 60 0.4
dark (all cells) transparent rgb(240,238,227) --nuri-bg-subtle #1a1913 36 / 60 1 / 0.4
  • Accent: the board's accent control was set to neutral, lilac and orange, in light and dark. All 6 combinations give identical ghost values.
  • CSS: the matched rules are the existing generic ones, .nuri-palette[data-variant="ghost"] { background: transparent; color: var(--nuri-text-primary) } and …[data-press-color]:active { background: var(--nuri-bg-subtle) }.
  • Pressed read: to read the pressed end colour, the 0.12s transition was set to none inline on the probe elements only.

RN-web probe (expo-demo expo start --web, Bitcoin tab, computed styles)

Cell Rest bg Text Pressed bg Height Opacity
swap sm (committed screen), light transparent #222013 rgb(251,249,238) #fbf9ee, scale 0.97 36 (paddingX 18, radius 9999) 1
lg, light transparent #222013 #fbf9ee, scale 0.97 60 1
sm accent="orange" / "lilac", light transparent #222013 #fbf9ee, scale 0.97 36 1
lg disabled, light transparent #222013 transparent, no scale 60 0.4
all cells, dark transparent rgb(240,238,227) rgb(26,25,19) #1a1913, scale 0.97 36 / 60 1 / 0.4
  • Reference: on the same screen, soft "Receive" is rgb(243,241,226).
  • Probe cells: the lg, accent and disabled cells come from a temporary probe block in Bitcoin.tsx. It was not committed, and the file was restored byte-identically (sha256 7f6cb4ae…).
  • Pressed read: pressed was read while holding a pointerdown.

Evidence (docs/evidence/button-ghost/)

Not verified

  • Native device or simulator. There is no iOS or Android render of ghost and no native pressed or disabled screenshot. RN coverage is react-test-renderer (jest) plus RN-web only.
  • Hit target. The check that the 36px sm swap button is easy to hit was not run. It needs a human tap on a device. If it fails, that is a separate request (hitSlop or size), per the decision.
  • Pressed screenshots. The pressed state is proven by computed-style reads, not screenshots.
  • Independent verification. This is the implementer's run. Independent verification by another session is still required before Verified/Done, and this PR does not self-certify it.

Follow-ups

  1. send-wip.html cleanup. It is not on origin/main (it lives on feat/playground-new-recipient-colour), so it is left out here. Once that branch is on main:
    • replace the 7 data-x-ghost … variant="soft" buttons with variant="ghost";
    • delete the two nuri-button[data-x-ghost] rules in <style data-outside-ds>, keeping .xpair, .xrow and the rest;
    • in the AMOUNT VARIANTS intro, say the button is now DS.
  2. Playground and expo-demo parity. packages/playground/pages/screens.html has the Bitcoin board that Bitcoin.tsx mirrors. It does not have the ghost swap yet, because that file is outside the admitted scope. Adding the same 4 lines there is an operator call.
  3. Expo web bundling fails on main. packages/rn/runtime/native-text-input-command.ts pulls AndroidTextInputNativeComponent into the web graph, which gives "Importing native-only module … on web". The RN-web probe above used the operator-local, untracked native-text-input-command.web.ts shim, copied in temporarily and removed afterwards (it is not in this PR). That fix should land on main separately.
  4. Release. Add the release-note line "Button: variant="ghost"" in the next rn/v0.x.y tag. Tagging is the operator's.
  5. nuri-expo adoption. That comes after the vendoring sync, as a separate consumer PR.

🤖 Generated with Claude Code

Implements the contract admitted in
#233 (comment) (sole-architect
exception · Project Ready confirmed by the operator). Reverses #154's
non-use drop of ghost from Button only; #154's stack fill/distribute and alert hug stay.

Spec
- button: `ghost` on ButtonAxes.variant and variants.variant (root palette.variant ghost). Default
  stays soft. No palette row, token, CSS or schema change (Guard F pin does not move).

Codegen + guards
- regenerated: the descriptor twin, the RN ButtonProps union ('solid' | 'soft' | 'ghost') and the
  variant row of the generated button doc page. No CSS, RN data or snapshot changed.
- Guard D axis pin moved (scripts/docs-drift.test.js); the doc page-contract pin moved and the
  page lead's four ghost points pinned (packages/doc/pipeline/docs-drift.test.js).
- RN: the three resolve cells #154 removed are back (transparent · pressed bg-subtle · fg
  text-primary) plus a ghost≡soft geometry + accent-invariance cell; geometry-bake pins that the
  cross-product covers ghost × sm|lg × fill; a new render-smoke ghost cell (light/dark, sm 36 / lg
  60, pressed, disabled, accent); type-tests pin the exact variant union and ghost (+accent) usage.

Docs
- button page lead: lowest emphasis (tertiary); for an action inside content where a fill would
  compete with it; never a screen's main or only action; accent does not paint it.

Consumers
- playground component/button: ghost in the variant board + a ghost proof board (sm swap, lg,
  accent orange, disabled).
- expo-demo Bitcoin screen: the other-unit amount under the balance is a sm ghost swap button.
- evidence: docs/evidence/button-ghost/ (web boards light/dark, RN-web screen + probe matrix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@darioaschero
darioaschero merged commit 23b4d66 into main Oct 29, 2026
5 checks passed
darioaschero added a commit that referenced this pull request Oct 29, 2026
… ghost button

Button variant="ghost" landed on main (#233, #234), so the
amount boards drop the page-CSS experiment.

send.html
- send · amount · bitcoin: the same send paid from Bitcoin. The amount in bitcoin sits
  flush under the big one as a sm ghost Button, ⇅ first; a tap swaps the units; the
  available line follows the currency of the amount ("€ 82.00 available"). Pure DS; one
  log line, no misfit.
- the header and the S2 comment point at the bitcoin case; the other variants stay in wip.

send-wip.html
- the seven amount boards use <nuri-button size="sm" variant="ghost">; the two
  nuri-button[data-x-ghost] rules and their comment are gone from <style data-outside-ds>.
- the AMOUNT VARIANTS intro and the bank · holder name log line say ghost is DS now.
- the From→To pair A/B/C experiments and their CSS stay on purpose, as evidence of the
  trials behind the finished boards.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
darioaschero added a commit that referenced this pull request Oct 29, 2026
…n-progress page (#235)

* feat(playground): send · amount board, send-wip page for the work in progress

send.html keeps the finished boards (recipient, scanner, search, new recipient, amount);
everything still in progress moves to send-wip.html, which replaces send-bank.html.

send.html
- send · amount: recipient as a two-line topbar title (md emphasis name, sm muted
  identifier), amount 3xl centred, the source as a subtle select-trigger with only the
  available balance under it (sm muted); no fee or arrival on this step, the review has
  them. Pure DS; two misfit lines logged.
- new recipient: the row subtitles name the identifier only ("Their @username", "IBAN",
  "Address or Lightning name"); no fee, arrival or invoice there.
- one closing </nuri-playground-shell> (a duplicate had slipped in).

send-wip.html
- amount variants per recipient (Nuri user, Bitcoin / Lightning / Ark address, Lightning
  invoice without amount, IBAN saved / new) and two states (empty, in bitcoin).
- experiments marked EXPERIMENT · OUTSIDE THE DS in a <style data-outside-ds> block:
  a ghost small button for the bitcoin/euro swap (data-x-ghost; the DS button has solid
  and soft only), the From→To pair sizes, the Coinbase-style and operator-mock boards.
- scanner A/B, the Bitcoin wallet form (five states), review switch variants, and the
  six IBAN boards from send-bank.html.

index.html: the "Send to bank" card points to "Send · work in progress".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(playground): send · amount · bitcoin board; send-wip uses the DS ghost button

Button variant="ghost" landed on main (#233, #234), so the
amount boards drop the page-CSS experiment.

send.html
- send · amount · bitcoin: the same send paid from Bitcoin. The amount in bitcoin sits
  flush under the big one as a sm ghost Button, ⇅ first; a tap swaps the units; the
  available line follows the currency of the amount ("€ 82.00 available"). Pure DS; one
  log line, no misfit.
- the header and the S2 comment point at the bitcoin case; the other variants stay in wip.

send-wip.html
- the seven amount boards use <nuri-button size="sm" variant="ghost">; the two
  nuri-button[data-x-ghost] rules and their comment are gone from <style data-outside-ds>.
- the AMOUNT VARIANTS intro and the bank · holder name log line say ghost is DS now.
- the From→To pair A/B/C experiments and their CSS stay on purpose, as evidence of the
  trials behind the finished boards.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant